#ifndef _PREFIX_C_
#define _PREFIX_C_

#define BR_PTHREADS 1

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include "prefix.h"

extern "C" {

#undef NULL
#define NULL ((void *) 0)
#define br_return_val_if_fail(expr,val) if (!(expr)) { \
    fprintf (stderr, "** BinReloc (%s): assertion %s failed\n", __PRETTY_FUNCTION__, #expr); return val; \
  }
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>
#include <pthread.h>

  char * br_locate( void *symbol ) {
    char line[5000];
    FILE *f;
    char *path;
    br_return_val_if_fail( symbol != NULL, ( char* )NULL );
    f = fopen( "/proc/self/maps", "r" );
    if( !f ) {
      return ( char* )NULL;
    }
    while( !feof( f ) ) {
      unsigned long start, end;
      if( !fgets( line, sizeof( line ), f ) ) {
        continue;
      }
      if( !strstr( line, " r-xp " ) || !strchr( line, '/' ) ) {
        continue;
      }
      sscanf( line, "%lx-%lx ", &start, &end );
      if( symbol >= ( void * ) start && symbol < ( void * ) end ) {
        char *tmp;
        size_t len;
        path = strchr( line, '/' );
        tmp = strrchr( path, '\n' );
        if( tmp ) {
          *tmp = 0;
        }
        len = strlen( path );
        if( len > 10 && strcmp( path + len - 10, " (deleted)" ) == 0 ) {
          tmp = path + len - 10;
          *tmp = 0;
        }
        fclose( f );
        return strdup( path );
      }
    }
    fclose( f );
    return ( char* )NULL;
  }
  char * br_locate_prefix( void *symbol ) {
    char *path, *prefix;
    br_return_val_if_fail( symbol != NULL, ( char* )NULL );
    path = br_locate( symbol );
    if( !path ) {
      return ( char* )NULL;
    }
    prefix = br_extract_prefix( path );
    free( path );
    return prefix;
  }
  char * br_prepend_prefix( void *symbol, char *path ) {
    br_return_val_if_fail( symbol != 0, ( char* )0 );
    br_return_val_if_fail( path != 0, ( char* )0 );
    char* tmp = br_locate_prefix( symbol );
    if( !tmp ) {
      return ( char* )0;
    }
    char *newpath;
    if( strcmp( tmp, "/" ) == 0 ) {
      newpath = strdup( path );
    } else
    { newpath = br_strcat( tmp, path ); }
    if( 0 ) {
      br_prepend_prefix( 0, ( char* )0 );
    }
    free( tmp );
    return newpath;
  }
  static pthread_key_t br_thread_key;
  static pthread_once_t br_thread_key_once = PTHREAD_ONCE_INIT;
  static void br_thread_local_store_fini() {
    char* specific = ( char * ) pthread_getspecific( br_thread_key );
    if( specific ) {
      free( specific );
      pthread_setspecific( br_thread_key, NULL );
    }
    pthread_key_delete( br_thread_key );
    br_thread_key = 0;
  }
  static void br_str_free( void *str ) {
    if( str ) {
      free( str );
    }
  }
  static void br_thread_local_store_init() {
    if( pthread_key_create( &br_thread_key, br_str_free ) == 0 ) {
      atexit( br_thread_local_store_fini );
    }
  }
  const char * br_thread_local_store( char *str ) {
    pthread_once( &br_thread_key_once, br_thread_local_store_init );
    char* specific = ( char * ) pthread_getspecific( br_thread_key );
    br_str_free( specific );
    pthread_setspecific( br_thread_key, str );
    return ( const char * ) str;
  }
  char * br_strcat( const char *str1, const char *str2 ) {
    if( !str1 ) {
      str1 = "";
    }
    if( !str2 ) {
      str2 = "";
    }
    size_t len1 = strlen( str1 );
    size_t len2 = strlen( str2 );
    char* result = ( char * ) malloc( len1 + len2 + 1 );
    memcpy( result, str1, len1 );
    memcpy( result + len1, str2, len2 );
    result[len1 + len2] = '\0';
    return result;
  }
  static char * br_strndup( char *str, size_t size ) {
    br_return_val_if_fail( str != ( char * ) 0, ( char * ) 0 );
    size_t len = strlen( str );
    if( !len ) {
      return strdup( "" );
    }
    if( size > len ) {
      size = len;
    }
    char* result = ( char * ) calloc( sizeof( char ), len + 1 );
    memcpy( result, str, size );
    return result;
  }
  char * br_extract_dir( const char *path ) {
    br_return_val_if_fail( path != ( char * ) 0, ( char * ) 0 );
    const char* end = strrchr( path, '/' );
    if( !end ) {
      return strdup( "." );
    }
    while( end > path && *end == '/' ) {
      end--;
    }
    char* result = br_strndup( ( char * ) path, end - path + 1 );
    if( !*result ) {
      free( result );
      return strdup( "/" );
    } else
    { return result; }
  }
  char * br_extract_prefix( const char *path ) {
    br_return_val_if_fail( path != ( char * ) 0, ( char * ) 0 );
    if( !*path ) {
      return strdup( "/" );
    }
    const char* end = strrchr( path, '/' );
    if( !end ) {
      return strdup( path );
    }
    char* tmp = br_strndup( ( char * ) path, end - path );
    if( !*tmp ) {
      free( tmp );
      return strdup( "/" );
    }
    end = strrchr( tmp, '/' );
    if( !end ) {
      return tmp;
    }
    char* result = br_strndup( tmp, end - tmp );
    free( tmp );
    if( !*result ) {
      free( result );
      result = strdup( "/" );
    }
    return result;
  }

}

#endif
